﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.Net.Sockets;

using mimosa;
using mimosa.Net;
using mimosa.SIP;
using mimosa.Utilities;
using mimosa.SIPServer;
using mimosa.SIP.Dialogue;
using log4net;

namespace MimosaV2Console
{
    public class StatefulDaemon
    {
        public const int CHECK_REGEXPIRY_DURATION = 2000;   // Period at which to check for expired registrations.

        private static ILog logger = AssemblyStateProxy.logger;
        private static string m_runtimeConfigFile = AssemblyStateProxy.RuntimeConfigFile;

        private static IPEndPoint m_proxyIPEndPoint = AssemblyStateProxy.ProxyIPEndPoint;
        private static IPEndPoint m_proxyContactIPAddress = AssemblyStateProxy.ProxyContactEndPoint;		// The connection socket the proxy should put in Record-Route headers it adds. Not required if the same as the socket the proxy is listening on.

        private static int m_monitorListenPort = AssemblyStateProxy.MonitorListenPort;
        private static int m_monitorEventPort = AssemblyStateProxy.MonitorEventPort;
        private static ProxyMonitorChannel m_monitorChannel = null;

        private static string m_switchDBConnStr = string.Format(AppState.GetConfigSetting("SIPSwitchDBConnStr"), "postgres", "8288");
        private static StorageTypes m_switchStorageType = StorageTypes.Postgresql;

        private static string m_logAllStr = AppState.GetConfigSetting("LogAll");
        private static bool m_logAll = false;

        private static string m_manglePrivateStr = AppState.GetConfigSetting("MangleClientContact");
        private static bool m_manglePrivate = true;

        private static string m_webServicePortStr = AppState.GetConfigSetting("WebServicePort");
        private int m_webServicePort = 10080;

        private static StorageTypes m_proxyLogDBStorageType = AssemblyStateProxy.ProxyLogStorageType;
        private static string m_proxyLogDBConnStr = AssemblyStateProxy.ProxyLogDBConnStr;
        private DatabaseLogger m_dbLogger = null;

        private static string m_registrarRealm = AssemblyStateProxy.RegistrarRealm;
        private static int m_registrarContactsPerUser = AssemblyStateProxy.RegistrarContactsPerUser;
        private static int m_registrarMaxExpiry = AssemblyStateProxy.RegistrarMaxExpiry;

        //private IPEndPoint m_secondarySTUNEndPoint = AssemblyStateProxy.STUNSeconD:\Phil_Project\sip-mimosa-v2\MimosaV2\StatefulDemon.csdaryEndPoint;
        //private STUNListener m_secondarySTUNListener;

        private IPEndPoint m_natKeepAliveEndPoint = AssemblyStateProxy.NATKeepAliveListenerEndPoint;
        private int m_natKeepAliveSendInterval = AssemblyStateProxy.NATKeepAliveSendInterval;
        private NATKeepAliveRelay m_natKeepAliveRelay;

        private string m_superUsername = AppState.GetConfigSetting("SuperUsername");

        StorageLayer m_sipSwitchStorageLayer = null;

        public StatefulDaemon()
        { }

        public void Start()
        {
            try
            {
                if (m_proxyIPEndPoint == null)
                {
                    throw new ApplicationException("Empty IP Address for proxy cannot start.");
                }
                else
                {
                    if (m_monitorEventPort != 0)
                    {
                        // Events will be sent by the monitor channel to the loopback interface and this port.
                        m_monitorChannel = new ProxyMonitorChannel(m_monitorEventPort);

                        if (m_monitorListenPort != 0)
                        {
                            // Listen for the monitor channel events and forward them onto clients.
                            IPEndPoint monitorEndPoint = new IPEndPoint(m_proxyIPEndPoint.Address, m_monitorListenPort);
                            ProxyMonitor proxyMonitor = new ProxyMonitor(new IPEndPoint[] { monitorEndPoint }, m_monitorEventPort, null, m_superUsername);
                            proxyMonitor.ProxyAuthenticate += new ProxyAuthenticateDelegate(proxyMonitor_ProxyAuthenticate);
                            proxyMonitor.StartMonitoring();
                        }
                    }

                    try
                    {
                        m_logAll = Convert.ToBoolean(m_logAllStr);
                    }
                    catch { }

                    try
                    {
                        m_manglePrivate = Convert.ToBoolean(m_manglePrivateStr);
                    }
                    catch { }

                    try
                    {
                        m_webServicePort = Convert.ToInt32(m_webServicePortStr);
                    }
                    catch { }

                    SIPTransport sipTransport = new SIPTransport(m_proxyIPEndPoint);
                    SIPTransport.SIPRequestInTraceEvent += new SIPTransportSIPRequestInTraceDelegate(LogSIPRequest);
                    SIPTransport.SIPRequestOutTraceEvent += new SIPTransportSIPRequestOutTraceDelegate(LogSIPRequest);
                    SIPTransport.SIPResponseInTraceEvent += new SIPTransportSIPResponseInTraceDelegate(LogSIPResponse);
                    SIPTransport.SIPResponseOutTraceEvent += new SIPTransportSIPResponseOutTraceDelegate(LogSIPResponse);

                    //m_secondarySTUNListener = new STUNListener(m_secondarySTUNEndPoint);   // This end point is only for secondary STUN messages.
                    //STUNServer stunServer = new STUNServer(m_proxyIPEndPoint, SIPTransport.Send, m_secondarySTUNEndPoint, m_secondarySTUNListener.Send);
                    //SIPTransport.STUNRequestReceived += new STUNRequestReceivedDelegate(stunServer.STUNPrimaryReceived);
                    //SIPTransport.STUNRequestReceived += new STUNRequestReceivedDelegate(LogPrimarySTUNRequestReceived);
                    SIPTransport.UnrecognisedMessageReceived += new UnrecognisedMessageReceivedDelegate(SIPTransport_UnrecognisedMessageReceived);
                    //m_secondarySTUNListener.MessageReceived += new STUNMessageReceived(stunServer.STUNSecondaryReceived);
                    //m_secondarySTUNListener.MessageReceived += new STUNMessageReceived(LogSecondarySTUNRequestReceived);

                    Console.WriteLine("SIP Proxy listening on UDP " + IPSocket.GetSocketString(m_proxyIPEndPoint));

                    m_sipSwitchStorageLayer = new StorageLayer(m_switchStorageType, m_switchDBConnStr);
                    m_sipSwitchStorageLayer.InitUserState();
                    m_sipSwitchStorageLayer.LoadTelecomVendor();

                    //RuntimeConfigBuilder configBuilder = new RuntimeConfigBuilder();
                    //SIPProxyRuntimeConfig runtimeProxy = configBuilder.InitialiseBareConfig(null);	
                    SIPRegistrationAgent regAgent = new SIPRegistrationAgent(m_switchStorageType, m_switchDBConnStr);

                    SIPSwitchRegistrarPersistor sipSwitchRegistrarPersistor = new SIPSwitchRegistrarPersistor(m_switchStorageType, m_switchDBConnStr, m_proxyIPEndPoint, false, m_monitorChannel);
                    sipSwitchRegistrarPersistor.StartPersistorThreads(1);
                    SIPRegistrations.LookupStoredRecord += new LoadPersistedRegistrarRecord(sipSwitchRegistrarPersistor.LoadPersistedRegistrarRecord);
                    SIPRegistrations.CheckStoredRecord += new CheckPersistedRegistrarRecord(SIPRegistrations_CheckStoredRecord);
                    SIPRegistrations.PersistRecord += new PersistRegistrarRecord(sipSwitchRegistrarPersistor.PersistRegistrationRecord);

                    SIPRegistrations.UserAgentConnected += new SIPUserAgentConnected(SIPRegistrations_UserAgentConnected);
                    SIPRegistrations.UserAgentDisconnected += new SIPUserAgentDisconnected(SIPRegistrations_UserAgentDisconnected);

                    RegistrarCore registrar = new RegistrarCore(m_monitorChannel, m_manglePrivate, false);
                    SIPRegistrarRecord.PerAddressBindings = m_registrarContactsPerUser;
                    SIPAddressBinding.UseTentativeDowns = false;

                    //SIPHeaderParserError parserError = SIPHeaderParserError.None;
                    //SIPRouteSet routeSet = new SIPRouteSet();
                    //routeSet.PushRoute(SIPRoute.ParseSIPRoute("sip:213.168.225.133;lr", out parserError));
                    //SIPAddressBinding.OptionsRequestRouteSet = routeSet;
                    //SIPAddressBinding.RegistrarMaxBindingExpiry = m_registrarMaxExpiry;

                    SIPRegistrations.AddDomain(m_registrarRealm);
                    SIPRegistrations.AddDomainAlias(SIPRegistrations.WILDCARD_DOMAIN, m_registrarRealm);

                    SIPRegistrations.AddDomainAlias(m_registrarRealm + ":5060", m_registrarRealm);
                    SIPRegistrations.AddDomainAlias(IPSocket.GetSocketString(m_proxyIPEndPoint), m_registrarRealm);

                    logger.Debug("Adding domain alias " + IPSocket.GetSocketString(m_proxyIPEndPoint) + " for " + m_registrarRealm + ".");
                    SIPRegistrations.AddDomainAlias(IPSocket.GetSocketString(m_proxyIPEndPoint), m_registrarRealm);
                    SIPRegistrations.AddDomainAlias(SIPRegistrations.WILDCARD_DOMAIN, m_registrarRealm);

                    if (m_proxyIPEndPoint.Port == 5060)
                    {
                        logger.Debug("Adding domain alias " + m_proxyIPEndPoint.Address.ToString() + " for " + m_registrarRealm + ".");
                        SIPRegistrations.AddDomainAlias(m_proxyIPEndPoint.Address.ToString(), m_registrarRealm);
                    }

                    if (m_natKeepAliveEndPoint != null)
                    {
                        m_natKeepAliveRelay = new NATKeepAliveRelay(m_natKeepAliveEndPoint, m_monitorChannel);
                    }

                    if (m_natKeepAliveEndPoint != null)
                    {
                        try
                        {
                            UdpClient natKeepAliveSender = new UdpClient();

                            SIPRegistrations.NATKeepAliveSender = natKeepAliveSender;
                            SIPRegistrations.NATKeepAliveSenderEndPoint = m_natKeepAliveEndPoint;
                            SIPRegistrations.NATKeepAliveSendInterval = m_natKeepAliveSendInterval;

                            logger.Debug("NATKeepAlives for SIP Registrar enabled, endpoint is " + m_natKeepAliveEndPoint + ".");
                        }
                        catch (Exception excp)
                        {
                            logger.Error("Exception StatefulProxy creating NATKeepAlive sender. " + excp.Message);
                        }
                    }

                    StatefulProxyCore statefulProxy = new StatefulProxyCore(sipTransport, m_monitorChannel, m_sipSwitchStorageLayer, null, regAgent, registrar);
                    statefulProxy.ManglePrivateAddresses = m_manglePrivate;
                    m_dbLogger = new DatabaseLogger(m_switchStorageType, m_switchDBConnStr);
                    statefulProxy.StatefulProxyLogEvent += new ProxyLogDelegate(statefulProxy_StatefulProxyLogEvent);

                    // Initialise random number to save delay on first SIP request.
                    //Crypto.GetRandomString();

                    

                    regAgent.Start();
                    Console.WriteLine("Registration agent started.");

                    //Thread bindingsThread = new Thread(new ThreadStart(PrintBindings));
                    //bindingsThread.Start();
                    ManualResetEvent stayAlive = new ManualResetEvent(false);
                    stayAlive.WaitOne();
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception Start. " + excp.Message);
                throw excp;
            }
        }

        string SIPRegistrations_CheckStoredRecord(SIPParameterlessURI addressOfRecord)
        {
            return this.m_sipSwitchStorageLayer.GetAcctStatus(addressOfRecord.User);
        }

        private bool proxyMonitor_ProxyAuthenticate(string username, string password)
        {
            Console.WriteLine("authenticating username=" + username + " and password=" + password + ".");

            if (username == null)
            {
                return false;
            }

            string usernameStr = Regex.Replace(username, "'", "''").ToUpper();
            string passwordStr = (password == null) ? null : Regex.Replace(password, "'", "''");

            string selectSQL = "select count(*) from customers where upper(username) = '" + usernameStr + "' and password = '" + passwordStr + "'";

            int count = Convert.ToInt32(m_sipSwitchStorageLayer.ExecuteScalar(selectSQL));

            if (count == 1)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private void statefulProxy_StatefulProxyLogEvent(ProxyMonitorEvent proxyLogEvent)
        {
            //if (proxyLogEvent != null && proxyLogEvent.ServerType != ProxyServerTypesEnum.RegisterAgent)
            //{
            if (m_dbLogger != null)
            {
                m_dbLogger.AddEvent(proxyLogEvent);
            }
            //}
        }

        private void SendMonitorEvent(ProxyMonitorEvent monitorEvent)
        {
            if (m_monitorChannel != null)
            {
                m_monitorChannel.Send(monitorEvent);
            }
        }

        private void LogSIPRequest(IPEndPoint localEndPoint, IPEndPoint endPoint, SIPRequest sipRequest)
        {
            try
            {
                if (m_logAll)
                {
                    logger.Debug(sipRequest.ToString());
                }

                //logger.Debug("LogSIPRequest " + sipRequest.Header.From.FromURI.User + ".");
                SendMonitorEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.FullSIPTrace, sipRequest.ToString(), sipRequest.Header.From.FromURI.User));
            }
            catch (Exception excp)
            {
                logger.Error("Exception LogSIPRequest. " + excp.Message);
            }
        }

        private void LogSIPResponse(IPEndPoint localEndPoint, IPEndPoint endPoint, SIPResponse sipResponse)
        {
            try
            {
                if (m_logAll)
                {
                    logger.Debug(sipResponse.ToString());
                }

                //logger.Debug("LogSIPResponse " + sipResponse.Header.From.FromURI.User + ".");
                SendMonitorEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.FullSIPTrace, sipResponse.ToString(), sipResponse.Header.From.FromURI.User));
            }
            catch (Exception excp)
            {
                logger.Error("Exception LogSIPResponse. " + excp.Message);
            }
        }

        private void LogPrimarySTUNRequestReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, byte[] buffer, int bufferLength)
        {
            logger.Debug("Primary STUN request recevied from " + IPSocket.GetSocketString(remoteEndPoint) + ".");
        }

        private void LogSecondarySTUNRequestReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, byte[] buffer, int bufferLength)
        {
            logger.Debug("Secondary STUN request recevied from " + IPSocket.GetSocketString(remoteEndPoint) + ".");
        }

        void SIPTransport_UnrecognisedMessageReceived(IPEndPoint localEndPoint, IPEndPoint fromEndPoint, byte[] buffer)
        {
            /*jhlee+++
            int bufferLength = (buffer != null) ? buffer.Length : 0;
            logger.Debug("Unrecognised packet received from " + IPSocket.GetSocketString(fromEndPoint) + " on " + IPSocket.GetSocketString(localEndPoint) + ", bytes=" + bufferLength + ".");

            if (bufferLength > 0)
            {
                logger.Debug("=>" + Encoding.ASCII.GetString(buffer) + "<=");
            }
            jhlee---*/
        }

        // 사용자가 접속해지(UnRegistration)
        void SIPRegistrations_UserAgentDisconnected(SIPParameterlessURI addressOfRecord)
        {
            logger.Debug(">>>>>>>>>> Disconnected User : " + addressOfRecord.ToString() + " <<<<<<<<<<<<<<<<");

            m_sipSwitchStorageLayer.UpdateUserStatus(addressOfRecord.User, "0000");

            string dialog_id = m_sipSwitchStorageLayer.GetDirtyDialogId(addressOfRecord.User);
            if (dialog_id != null && dialog_id != string.Empty)
            {
                SIPDialogue.RemoveDialogue(dialog_id);
            }

            m_sipSwitchStorageLayer.UpdateExpiredDialog(addressOfRecord.User);
        }

        // 사용자가 접속(Registration)
        void SIPRegistrations_UserAgentConnected(SIPRegistrarRecord registrarRecord)
        {
            logger.Debug(">>>>>>>>>> Connected User : " + registrarRecord.AddressOfRecord.ToString() + "<<<<<<<<<<<<<<<<");

            m_sipSwitchStorageLayer.UpdateUserStatus(registrarRecord.AddressOfRecord.User, "0001");
            m_sipSwitchStorageLayer.UpdateExpiredDialog(registrarRecord.AddressOfRecord.User);
        }
    }
}
